package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.exception.CustomException;
import com.heima.common.redis.CacheService;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskInfoLogsMapper;
import com.heima.schedule.mapper.TaskInfoMapper;
import com.heima.schedule.service.TaskService;
import com.sun.org.apache.bcel.internal.generic.NEW;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.Date;

@Service
@Slf4j
public class TaskServiceImpl implements TaskService {

    @Autowired
    TaskInfoMapper taskInfoMapper;
    @Autowired
    TaskInfoLogsMapper taskInfoLogsMapper;
    @Autowired
    CacheService cacheService;


    @Override
    @Transactional
    public long addTask(Task task) {
        //1.保存任务到db
        Long taskId = addTaskToDB(task);

        //2.保存任务到redis
        task.setTaskId(taskId);
        addTaskToRedis(task);

        //3.返回任务主键
        return taskId;
    }

    @Override
    public boolean cancelTask(long taskId) {

        //1.操作db  删除taskinfo表中该任务   修改taskinfo_logs表中该任务状态为2
        Task task = updateTaskDb(taskId, ScheduleConstants.CANCELLED);

        //2.操作redis  删除
        removeRedisTask(task);

        return true;
    }

    @Override
    public Task poll(int type, int priority) {
        //从redis中list接口中  消费任务
        String key = type +"_"+ priority;
        String taskId = cacheService.lRightPop(ScheduleConstants.TOPIC + key);
        Task task = null;
        if(StringUtils.isNotBlank(taskId)){
            //pop 是 taskid  任务id  字符串
            //根据任务id  从hash结构中获取任务详情
            String taskJSONStr = (String)cacheService.hGet("hash_" + key, taskId);//任务的json格式字符串
            task = JSON.parseObject(taskJSONStr, Task.class);

            //操作db  删除任务表数据   修改任务日志表状态为1
            updateTaskDb(task.getTaskId(), ScheduleConstants.EXECUTED);
        }
        return task;
    }

    /**
     * 删除redis中的任务数据
     *
     * @param task
     */
    private void removeRedisTask(Task task) {
        //1.拼接redis中key
        String key = task.getTaskType() + "_" + task.getPriority();

        if (task.getExecuteTime() <= System.currentTimeMillis()) {
            //从list中删除任务
            cacheService.lRemove(ScheduleConstants.TOPIC + key, 0, task.getTaskId().toString());
        } else {
            //从zset中删除
            cacheService.zRemove(ScheduleConstants.FUTURE + key, task.getTaskId().toString());
        }
        //删除hash结构中 任务详情数据
        cacheService.hDelete("hash_" + key, task.getTaskId().toString());

    }

    /**
     * 修改任务状态  删除taskinfo  修改taskinfo_logs
     *
     * @param taskId
     * @param staus  0是待执行   1是已执行   2取消
     * @return
     */
    private Task updateTaskDb(long taskId, int staus) {

        taskInfoMapper.deleteById(taskId);
        TaskinfoLogs taskinfoLogs = taskInfoLogsMapper.selectById(taskId);
        taskinfoLogs.setStatus(staus);
        int update = taskInfoLogsMapper.updateById(taskinfoLogs);
        if (update <= 0) {
            //说明修改失败  版本号不一致
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR);
        }
        Task task = new Task();
        task.setTaskId(taskinfoLogs.getTaskId());
        task.setTaskType(taskinfoLogs.getTaskType());
        task.setPriority(taskinfoLogs.getPriority());
        task.setParameters(taskinfoLogs.getParameters());
        task.setExecuteTime(taskinfoLogs.getExecuteTime().getTime());
        return task;
    }

    /**
     * 添加任务到redis
     *
     * @param task
     */
    private void addTaskToRedis(Task task) {
        //1.拼接redis中key
        String key = task.getTaskType() + "_" + task.getPriority();

        //2.判断任务的执行时间  小于当前时间存list结构  大于当前时间小于未来5分钟存在zset    如果未来5分钟后执行先存db
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, 5);
        long timeInMillis = instance.getTimeInMillis();//获取未来5分钟那个时间点  long类型时间

        boolean flag = false;//代表任务详情需不需存redis
        if (task.getExecuteTime() <= System.currentTimeMillis()) {
            //说明任务需要立刻执行  存list结构
            //topic_1001_1 代表交话费任务    topic_1002_1 代表冲点卡任务    topic_1003_1 代表冲油费任务
            cacheService.lLeftPush(ScheduleConstants.TOPIC + key, task.getTaskId().toString());
            flag = true;
        } else if (task.getExecuteTime() <= timeInMillis) {
            //说明任务是未来5分钟内要执行  存zset结构
            //future_1004_1  代表定时发布文章    future_1005_1 代表XXXX任务
            // future_1004_1      任务1iid    3.11 15.25      任务2 id    3.11 15.26
            // redis
            cacheService.zAdd(ScheduleConstants.FUTURE + key, task.getTaskId().toString(), task.getExecuteTime());
            flag = true;
        }

        //3.如果存到redis结构中，将任务详情存redis  hash结构
        if (flag) {
            cacheService.hPut("hash_" + key, task.getTaskId().toString(), JSON.toJSONString(task));
            //修改任务表中im_redis字段为 1   默认是0
            taskInfoMapper.update(null, Wrappers.<Taskinfo>lambdaUpdate()
                    .set(Taskinfo::getInRedis, 1)
                    .eq(Taskinfo::getTaskId, task.getTaskId()));
        }
    }

    /**
     * 保存任务到db中  taskinfo    taskinfo_logs
     *
     * @param task
     * @return
     */
    private Long addTaskToDB(Task task) {

        Taskinfo taskinfo = new Taskinfo();
        BeanUtils.copyProperties(task, taskinfo);
        taskinfo.setExecuteTime(new Date(task.getExecuteTime()));
        taskInfoMapper.insert(taskinfo);

        TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
        BeanUtils.copyProperties(taskinfo, taskinfoLogs);
        taskinfoLogs.setVersion(1);
        taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);
        taskInfoLogsMapper.insert(taskinfoLogs);

        return taskinfo.getTaskId();
    }
}
